package net.maxupeng.asm74;

import lombok.Data;
import net.maxupeng.asm74.util.JsonUtil;
import net.maxupeng.asm74.util.NumberUtil;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Data
public class MCodeDefModel {
    @Data
    public static class Instruction {
        public String name;
        public String code;
        public Map<String, List<String>> am;
        public Map<String, Map<String, List<String>>> when;
    }

    @Data
    public class ASMInstruction {
        public int pos;

        public String opcode;
        public String am;
        public int addr;
        public int value;

        public ASMInstruction(int pos, String opcode, String am, int addr, int value) {
            this.pos = pos;
            this.opcode = opcode;
            this.am = am;
            this.addr = addr;
            this.value = value;
        }

        public byte[] toBinary() {
            return getBinaryCode(opcode, am, addr, value);
        }

        public int length() {
            return getInstructionLength(opcode, am);
        }
    }

    public int instruction_bits;
    public int am_bits;
    public int step_bits;
    public List<String> signals;
    public List<String> flags;
    public List<String> ams;
    public Map<String, String> single_macros;
    public Map<String, List<String>> list_macros;
    public List<Instruction> instructions;

    public static MCodeDefModel fromJson(String json) {
        return JsonUtil.fromJson(json, MCodeDefModel.class);
    }

    private Map<String, Integer> instructionMap = null;

    public Map<String, Integer> getInstructionMap() {
        if (instructionMap != null) {
            return instructionMap;
        }

        instructionMap = new HashMap<>();
        instructions.forEach(instruction -> {
            instructionMap.put(instruction.name, NumberUtil.str2int(instruction.code));
        });
        return instructionMap;
    }

    private Map<String, Integer> amMap = null;

    public Map<String, Integer> getAmMap() {
        if (amMap != null) {
            return amMap;
        }

        amMap = new HashMap<>();

        for (int i = 0; i < ams.size(); i++) {
            amMap.put(ams.get(i), i);
        }

        return amMap;
    }

    public byte[] getBinaryCode(String opcode, String am, int address, int value) {
        int o = getInstructionMap().get(opcode);
        int a = getAmMap().get(am);
        switch (am) {
            case "default":
                return new byte[]{(byte) ((o << 3 | a) & 0xff)};
            case "imm":
                return new byte[]{(byte) ((o << 3 | a) & 0xff), (byte) value};
            case "direct":
            case "indirect":
                return new byte[]{(byte) ((o << 3 | a) & 0xff), (byte) (address & 0xff), (byte) ((address >> 8) & 0xff)};
            case "direct_index":
            case "indirect_index":
            case "index_indirect":
                return new byte[]{(byte) ((o << 3 | a) & 0xff), (byte) value, (byte) (address & 0xff), (byte) ((address >> 8) & 0xff)};
        }
        return new byte[0];
    }

    public static int getInstructionLength(String opcode, String am) {
        switch (am) {
            case "default":
                return 1;
            case "imm":
                return 2;
            case "direct":
            case "indirect":
                return 3;
            case "direct_index":
            case "indirect_index":
            case "index_indirect":
                return 4;
            default:
                System.out.println("Unknown Addressing Mode: " + am);
                System.exit(1);
        }
        return 0;
    }
}
